// ----------------------------------------------------------------------------
// serialize_unit_test.cpp
// ----------------------------------------------------------------------------
#include <fw/base/core/macros.h>
#if defined(FW_ENABLE_UNIT_TEST)
#include <fw/base/core/debugconsole.h>
#include <gtest/gtest.h>
#include <fw/base/serialize/chunk.h>
#include <fw/base/serialize/chunk_file.h>
#include <fw/base/serialize/serialize_allocator.h>
#include <fw/base/serialize/serialize_schema.h>

using namespace fw;

namespace
{
	void ChunkTest(const Chunk* root,
				   const char* data0, uint32_t dataSize0,
				   const char* data1, uint32_t dataSize1,
				   const char* data2, uint32_t dataSize2,
				   const char* data3, uint32_t dataSize3,
				   const char* data4, uint32_t dataSize4)
	{
		const Chunk* c = root;
		
		ASSERT_EQ(c->GetChildCount(), 3);
		ASSERT_EQ(c->GetDataSize(), 0);
		
		// child node.
		c = c->GetChild();
		
		ASSERT_EQ(c->GetChildCount(), 0);
		ASSERT_EQ(c->GetDataSize(), dataSize0);
		ASSERT_EQ(strcmp(reinterpret_cast<const char*>(c->GetData()), data0), 0);
		
		c = c->GetSibling();
		
		ASSERT_EQ(c->GetChildCount(), 0);
		ASSERT_EQ(c->GetDataSize(), dataSize1);
		ASSERT_EQ(strcmp(reinterpret_cast<const char*>(c->GetData()), data1), 0);
		
		c = c->GetSibling();
		
		ASSERT_EQ(c->GetChildCount(), 3);
		ASSERT_EQ(c->GetDataSize(), 0);
		
		const Chunk* terminate = c->GetSibling();
		
		ASSERT_EQ(terminate, nullptr);
		
		// Grand child node.
		c = c->GetChild();
		
		ASSERT_EQ(c->GetChildCount(), 0);
		ASSERT_EQ(c->GetDataSize(), dataSize2);
		ASSERT_EQ(strcmp(reinterpret_cast<const char*>(c->GetData()), data2), 0);
		
		c = c->GetSibling();
		
		ASSERT_EQ(c->GetChildCount(), 0);
		ASSERT_EQ(c->GetDataSize(), dataSize3);
		ASSERT_EQ(strcmp(reinterpret_cast<const char*>(c->GetData()), data3), 0);
		
		c = c->GetSibling();
		
		ASSERT_EQ(c->GetChildCount(), 0);
		ASSERT_EQ(c->GetDataSize(), dataSize4);
		ASSERT_EQ(strcmp(reinterpret_cast<const char*>(c->GetData()), data4), 0);
		
		terminate = c->GetSibling();
		
		ASSERT_EQ(terminate, nullptr);
	}
	
	
} // unnamed namespace

TEST(serialize, Chunk)
{
	const uint32_t rootID    = FW_CHUNK_ID('R', 'O', 'O', 'T');
	const uint32_t childID   = FW_CHUNK_ID('C', 'H', 'L', 'D');
	
	const uint32_t catsID    = FW_CHUNK_ID('c', 'a', 't', 's');
	const uint32_t dogsID    = FW_CHUNK_ID('d', 'o', 'g', 's');
	const uint32_t boysID    = FW_CHUNK_ID('b', 'o', 'y', 's');
	const uint32_t girlsID   = FW_CHUNK_ID('g', 'r', 'l', 's');
	const uint32_t elderlyID = FW_CHUNK_ID('e', 'l', 'd', 'y');
	
	const char* catsData  = "My Kitten.";
	const char* dogsData  = "My Puppy.";
	const char* boysData  = "The boy is sleeping in the arms of his mother.";
	const char* girlsData = "The girl cackled with glee.";
	const char* elderlyData = "The elderly woman is walking alone.";
	
	// +1 : null終端子のサイズ.
	uint32_t catsDataSize  = static_cast<uint32_t>(strlen(catsData)) + 1;
	uint32_t dogsDataSize  = static_cast<uint32_t>(strlen(dogsData)) + 1;
	uint32_t boysDataSize  = static_cast<uint32_t>(strlen(boysData)) + 1;
	uint32_t girlsDataSize = static_cast<uint32_t>(strlen(girlsData))+ 1;
	uint32_t elderlyDataSize = static_cast<uint32_t>(strlen(elderlyData))+ 1;
	
	Chunk* root = new Chunk(rootID);
	ASSERT_NE(root, nullptr);
	
	root->AddChild(new Chunk(catsID, catsData, catsDataSize));
	root->AddChild(new Chunk(dogsID, dogsData, dogsDataSize));
	
	Chunk* child = root->AddChild(new Chunk(childID));
	ASSERT_NE(child, nullptr);

	child->AddChild(new Chunk(boysID, boysData, boysDataSize));
	child->AddChild(new Chunk(girlsID, girlsData, girlsDataSize));
	child->AddChild(new Chunk(elderlyID, elderlyData, elderlyDataSize));
	
	ChunkTest(root,
			  catsData, catsDataSize,
			  dogsData, dogsDataSize,
			  boysData, boysDataSize,
			  girlsData,girlsDataSize,
			  elderlyData, elderlyDataSize);
	
	// チャンクデータのファイルへの保存.
	static const char* s_fileName = "TestChunkFile.chunk";
	
	remove(s_fileName);
	
	FILE* file = fopen(s_fileName, "rb");
	bool exist = file != nullptr;
	if(exist)
	{
		fclose(file);
		ASSERT_EQ(1, 0);// s_fileName が存在してしまっている.
		return;
	}
	
	int fileSize = ChunkWrite(s_fileName, root);
	ASSERT_NE(fileSize, 0);
	
	if(root)
	{
		delete root; // ルートノードを削除することで、子供ノードも削除される.
	}
	
	// チャンクデータのファイルからの読み込み.
	char fileBuffer[2048];
	const Chunk* readRoot = ChunkRead(s_fileName, fileBuffer, sizeof(fileBuffer));
	ASSERT_NE(readRoot, nullptr);
	
	ChunkTest(readRoot,
			  catsData, catsDataSize,
			  dogsData, dogsDataSize,
			  boysData, boysDataSize,
			  girlsData,girlsDataSize,
			  elderlyData, elderlyDataSize);
	
	if(readRoot)
	{
		delete readRoot;
	}
}

namespace Src
{
	struct Struct
	{
		uint8_t		u8;
		float		f32;
		uint16_t	u16;
		uint32_t	u32;
		uint64_t	u64;
		int8_t		i8;
		double		f64;
		int16_t		i16;
		int32_t		i32;
		int64_t		i64;
	}; // struct Src::Struct

} // namespace Src

namespace Dst
{
	struct Struct
	{
		int8_t		i8;
		double		f64;
		int16_t		i16;
		int32_t		i32;
		int64_t		i64;
		uint8_t		u8;
		float		f32;
		uint16_t	u16;
		uint32_t	u32;
		uint64_t	u64;
	}; // struct Src::Struct
	
} // namespace Dst


// 異なるスキーマ間のデータのコンバートのテスト. バージョンの異なるデータの読み込みなどを想定している.
TEST(serialize, ConvertFlat)
{
	// 保存元スキーマの定義. 実際はスキーマの定義は fw/base/reflection などを使って自動化する.
	SerializeSchema srcSchema;
	
	srcSchema.Begin();
	srcSchema.BeginCompound("Struct");
	srcSchema.AddInstance("u8",  1, "uint8_t" );
	srcSchema.AddInstance("f32", 1, "float"   );
	srcSchema.AddInstance("u16", 1, "uint16_t");
	srcSchema.AddInstance("u32", 1, "uint32_t");
	srcSchema.AddInstance("u64", 1, "uint64_t");
	srcSchema.AddInstance("i8",  1, "int8_t"  );
	srcSchema.AddInstance("f64", 1, "double"  );
	srcSchema.AddInstance("i16", 1, "int16_t" );
	srcSchema.AddInstance("i32", 1, "int32_t" );
	srcSchema.AddInstance("i64", 1, "int64_t" );
	srcSchema.EndCompound();
	srcSchema.End();
	
	//! 保存先スキーマの定義.
	SerializeSchema dstSchema;
	
	dstSchema.Begin();
	dstSchema.BeginCompound("Struct");
	dstSchema.AddInstance("i8",  1, "int8_t"  );
	dstSchema.AddInstance("f64", 1, "double"  );
	dstSchema.AddInstance("i16", 1, "int16_t" );
	dstSchema.AddInstance("i32", 1, "int32_t" );
	dstSchema.AddInstance("i64", 1, "int64_t" );
	dstSchema.AddInstance("u8",  1, "uint8_t" );
	dstSchema.AddInstance("f32", 1, "float"   );
	dstSchema.AddInstance("u16", 1, "uint16_t");
	dstSchema.AddInstance("u32", 1, "uint32_t");
	dstSchema.AddInstance("u64", 1, "uint64_t");
	dstSchema.EndCompound();
	
	dstSchema.End();
	
	// 元のメモリイメージとスキーマ情報から新形式のデータを構築する. 
	Src::Struct srcStruct;
	srcStruct.u8	= 200;
	srcStruct.f32	= 3.141f;
	srcStruct.u16	= 60000;
	srcStruct.u32	= 123456;
	srcStruct.u64	= 0xdeadbeefdeadbeefULL;
	srcStruct.i8	= -100;
	srcStruct.f64	= 1.23456789;
	srcStruct.i16	= -30000;
	srcStruct.i32	= 0x01234567;
	srcStruct.i64	= 0x1bcdabcdabcdabcdLL;
	
	char buffer[1000];
	SerializeAllocator alloc(buffer, sizeof(buffer));
	
	Dst::Struct* dstStruct =
		reinterpret_cast<Dst::Struct*>(dstSchema.Convert(reinterpret_cast<const char*>(&srcStruct),
														 srcSchema,
														 "Struct",
														 alloc));
	
	ASSERT_EQ(dstStruct->u8,  200);
	ASSERT_EQ(dstStruct->f32, 3.141f);
	ASSERT_EQ(dstStruct->u16, 60000);
	ASSERT_EQ(dstStruct->u32, 123456);
	ASSERT_EQ(dstStruct->u64, 0xdeadbeefdeadbeefULL);
	ASSERT_EQ(dstStruct->i8,  -100);
	ASSERT_EQ(dstStruct->f64, 1.23456789);
	ASSERT_EQ(dstStruct->i16, -30000);
	ASSERT_EQ(dstStruct->i32, 0x01234567);
	ASSERT_EQ(dstStruct->i64, 0x1bcdabcdabcdabcdLL);
}

TEST(serialize, ConvertDeep)
{
    
}

#endif // FW_ENABLE_UNIT_TEST

void SERIALIZE_UNIT_TEST()
{
}
